/**
 * 
 */
package io;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.util.TreeSet;

import dataTypes.ISequenceData;
import dataTypes.PyroData;
import exception.DataLengthException;

/**
 * A reader to load fasta sequences and corresponding fasta phred quality file.
 * 
 * @author Christian Johansen
 * 
 */
public class FastaReader implements ISequenceReader {

	private StreamTokenizer seq;
	private StreamTokenizer quality;

	private File seqFile;
	private File qualityFile;

	/**
	 * @param seqFile
	 *            the fasta file containing the actual sequences.
	 * @param qualityFile
	 *            the fasta file containing the phred formated qualities.
	 * @throws FileNotFoundException
	 */
	public FastaReader(File seqFile, File qualityFile)
			throws FileNotFoundException {

		this.seqFile = seqFile;
		this.qualityFile = qualityFile;
		seq = new StreamTokenizer(new BufferedReader(new FileReader(seqFile)));
		quality = new StreamTokenizer(new BufferedReader(new FileReader(
				qualityFile)));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see io.ISequenceReader#loadNext()
	 */
	public ISequenceData loadNext() throws IOException {
		// System.out.println("EOF"+StreamTokenizer.TT_EOF+"EOL"+StreamTokenizer.TT_EOL+"Num"+StreamTokenizer.TT_NUMBER+"Word"+StreamTokenizer.TT_WORD);
		// EOF -1 EOL 10 Num -2 Word -3

		if (quality.ttype == StreamTokenizer.TT_EOF
				|| seq.ttype == StreamTokenizer.TT_EOF) {
			return null;
		}

		if (quality.ttype != 62) {
			while (quality.nextToken() != 62) {
				// Just skip to next sequence. Should always only be done once
				// or
				// not at all.
			}
		}
		if (seq.ttype != 62) {
			while (seq.nextToken() != 62) {
				// Just skip to next sequence. Should always only be done once
				// or
				// not at all.
			}
		}
		return internalReadNext(seq, quality);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see io.ISequenceReader#readAllNames()
	 */
	public TreeSet<String> readAllNames() throws IOException {

		TreeSet<String> names = new TreeSet<String>();

		StreamTokenizer seqName = new StreamTokenizer(new BufferedReader(
				new FileReader(seqFile)));
		StreamTokenizer qualityName = new StreamTokenizer(new BufferedReader(
				new FileReader(qualityFile)));
		String tempName = "";
		while (seqName.ttype != StreamTokenizer.TT_EOF
				&& qualityName.ttype != StreamTokenizer.TT_EOF) {

			while (seqName.nextToken() != 62
					&& seqName.ttype != StreamTokenizer.TT_EOF) {
			}
			if (seqName.ttype != StreamTokenizer.TT_EOF) {
				seqName.nextToken();
				tempName = seqName.sval;

				while (qualityName.nextToken() != 62
						&& qualityName.ttype != StreamTokenizer.TT_EOF) {
				}
				if (qualityName.ttype != StreamTokenizer.TT_EOF) {

					qualityName.nextToken();
					if (qualityName.sval.compareTo(tempName) != 0) {
						throw new IOException(
								"Mismatch between files at or near '"
										+ tempName + "'. (does not match '"
										+ qualityName.sval + "')");
					} else {
						names.add(tempName);
					}
				} else {
					throw new IOException("Unexpected end of quality file");
				}
			} else {
				while (qualityName.nextToken() != 62
						&& qualityName.ttype != StreamTokenizer.TT_EOF) {
				}
				if (qualityName.ttype != StreamTokenizer.TT_EOF) {
					throw new IOException("Unexpected end of sequence file");
				}
			}
		}
		return names;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see io.ISequenceReader#loadSequence(java.lang.String)
	 */
	public ISequenceData loadSequence(String geneName) throws IOException {
		StreamTokenizer seq = new StreamTokenizer(new BufferedReader(
				new FileReader(seqFile)));
		StreamTokenizer quality = new StreamTokenizer(new BufferedReader(
				new FileReader(qualityFile)));
		while (seq.ttype != StreamTokenizer.TT_EOF
				&& quality.ttype != StreamTokenizer.TT_EOF) {
			while (seq.nextToken() != 62 && seq.ttype != StreamTokenizer.TT_EOF) {
			}
			while (quality.nextToken() != 62
					&& quality.ttype != StreamTokenizer.TT_EOF) {
			}
			if (seq.ttype != StreamTokenizer.TT_EOF
					&& quality.ttype != StreamTokenizer.TT_EOF) {
				seq.nextToken(); // Should be name
				quality.nextToken();// Should be name
				if (seq.sval.compareToIgnoreCase(geneName) == 0) {
					seq.pushBack();
					quality.pushBack();
					return internalReadNext(seq, quality);
				}
			}
		}
		throw new IOException(geneName + " not found in loaded data file!");
	}

	/**
	 * Reads the next sequence and qualities from the given StreamTokeizers.
	 * 
	 * @param seq
	 *            sequence stream.
	 * @param quality
	 *            quality stream.
	 * @return a ISequenceData containing the data read.
	 * @throws IOException
	 */
	private ISequenceData internalReadNext(StreamTokenizer seq,
			StreamTokenizer quality) throws IOException {

		quality.nextToken(); // Name
		seq.nextToken(); // Name

		if (quality.sval.compareTo(seq.sval) != 0) {
			throw new IOException("Mismatch between sequence and quality file.");
		}
		String seqName = quality.sval;

		quality.nextToken(); // Should be rank
		seq.nextToken();

		if (quality.sval.compareToIgnoreCase("rank") != 0) {
			throw new IOException("Corrupt quality file");
		}

		if (seq.sval.compareToIgnoreCase("rank") != 0) {
			throw new IOException("Corrupt sequence file");
		}

		quality.nextToken(); // =
		seq.nextToken(); // =

		quality.nextToken(); // should be value of rank
		seq.nextToken(); // should be value of rank

		if (quality.nval != seq.nval) {
			throw new IOException("Mismatch between sequence and quality file.");
		}
		int seqRank = ((int) quality.nval);

		// TODO: Fix checks that the values are the same in both files.

		quality.nextToken(); // x
		seq.nextToken();
		quality.nextToken(); // =
		seq.nextToken();
		quality.nextToken(); // x-value
		seq.nextToken();
		double seqXCoordinate = quality.nval;

		quality.nextToken(); // y
		seq.nextToken();
		quality.nextToken(); // =
		seq.nextToken();
		quality.nextToken(); // y-value
		seq.nextToken();
		double seqYCoordinate = quality.nval;

		quality.nextToken(); // length
		seq.nextToken();
		quality.nextToken(); // =
		seq.nextToken();
		quality.nextToken(); // value of length
		seq.nextToken();
		int seqLength = (int) quality.nval;

		String seqDataString = "";

		while (seq.nextToken() != 62 && seq.ttype != StreamTokenizer.TT_EOF) {
			seqDataString += seq.sval;
		}

		byte[] seqData = seqDataString.getBytes("UTF-8");

		byte[] seqQuality = new byte[seqLength];

		for (int i = 0; i < seqLength; i++) {
			quality.nextToken();
			if (quality.ttype == 62 || quality.ttype == StreamTokenizer.TT_EOF) {
				throw new IOException("Corrupted datafile at line "
						+ quality.lineno());
			}
			seqQuality[i] = (byte) quality.nval;
		}
		quality.nextToken(); // Should be >

		try {
			return new PyroData(seqName, seqLength, seqXCoordinate,
					seqYCoordinate, seqData, seqQuality, seqRank);
		} catch (DataLengthException e) {
			throw new IOException(e.getMessage());
		}
	}
}
